Skip to content

Hlsl bxdfs 3 #899

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 160 commits into
base: master
Choose a base branch
from
Open

Hlsl bxdfs 3 #899

wants to merge 160 commits into from

Conversation

devshgraphicsprogramming
Copy link
Member

Description

Continuing #811 due to GH UI messing up diffs again

Testing

TODO list:

const scalar_type sampleProb = nbl::hlsl::dot<spectral_type>(sampleValue,luminosityContributionHint);

const scalar_type _pdf = bit_cast<scalar_type, uint32_t>(numeric_limits<scalar_type>::infinity);
return quotient_pdf_type::create((spectral_type)(sampleValue / sampleProb), _pdf);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sampleValue is a spectral_type which should already have an operator/(scalar_type) no need to cast


ray_dir_info_type L;
L.direction = (transmitted ? (vector3_type)(0.0) : N * 2.0f * NdotV) - V;
return sample_type::create(L, nbl::hlsl::dot<vector3_type>(V, L.direction), T, B, N);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VdotL can be computed much more cheaply as (transmitted ? 0.f:2.f)*NdotV*NdotV-1.f;

Comment on lines +38 to +91
template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>)
struct SAnisotropicParams
{
using this_t = SAnisotropicParams<T>;

static this_t create(T NdotH, T one_minus_NdotH2_rcp, T TdotH2, T BdotH2, T nx, T ny) // blinn-phong
{
this_t retval;
retval.NdotH = NdotH;
retval.one_minus_NdotH2_rcp = one_minus_NdotH2_rcp;
retval.TdotH2 = TdotH2;
retval.BdotH2 = BdotH2;
retval.nx = nx;
retval.ny = ny;
return retval;
}

static this_t create(T ax, T ay, T ax2, T ay2, T TdotH2, T BdotH2, T NdotH2) // beckmann, ggx aniso
{
this_t retval;
retval.ax = ax;
retval.ax2 = ax2;
retval.ay = ay;
retval.ay2 = ay2;
retval.TdotH2 = TdotH2;
retval.BdotH2 = BdotH2;
retval.NdotH2 = NdotH2;
return retval;
}

static this_t create(T a2, T TdotH, T BdotH, T NdotH) // ggx burley
{
this_t retval;
retval.ax = a2;
retval.TdotH = TdotH;
retval.BdotH = BdotH;
retval.NdotH = NdotH;
return retval;
}

T ax;
T ay;
T ax2;
T ay2;
T nx;
T ny;
T NdotH;
T TdotH;
T BdotH;
T NdotH2;
T TdotH2;
T BdotH2;
T one_minus_NdotH2_rcp;
};
Copy link
Member Author

@devshgraphicsprogramming devshgraphicsprogramming Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Microfacet caches already have getters for all the dotH

Do not conflate per-ray and per-material values together.

Each NDF needs to have its own parameter struct with basic members (e.g. roughness) and expands that to more members inside itself via create if needed

Right now you've made a frankenstein that makes GGX keep BlinnPhong nx,ny exponents and Blinn Phong keep ax,ay roughnesses

Same with one_minus_NdotH2 only certain NDFs (seems Ashikhmin-shirley only) need it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the dotH can be rolled into a proto-microfacet cache structs

scalar_type operator()(SAnisotropicParams<scalar_type> params)
{
scalar_type n = (params.TdotH2 * params.ny + params.BdotH2 * params.nx) * params.one_minus_NdotH2_rcp;
return (isinf<scalar_type>(params.nx) || isinf<scalar_type>(params.ny)) ? numeric_limits<scalar_type>::infinity :
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its enough to check that n is "large enough" e.g. >254 because pow with exponent higher than 254 is bound to make a lowest non denormalized number into an inf

};


template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you actually need FloatingPointScalar on all the NDF, surely you don't want them to be creatable with integers and booleans, right ?

Comment on lines +97 to +98
using scalar_type = T;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add some static functions to convert phong exponent to roughness squared and back, according to Walter et al.
https://github.com/Devsh-Graphics-Programming/Nabla/blob/1314fb0d05d85c2df8400733e4ff5602746a06a0/include/nbl/builtin/hlsl/bxdf/ndf/blinn_phong.hlsl

Comment on lines +111 to +144
sqrt<scalar_type>((params.nx + 2.0) * (params.ny + 2.0)) * numbers::inv_pi<scalar_type> * 0.5 * pow<scalar_type>(params.NdotH, n);
}
};

template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>)
struct Beckmann
{
using scalar_type = T;

scalar_type operator()(SIsotropicParams<scalar_type> params)
{
scalar_type nom = exp<scalar_type>( (params.NdotH2 - 1.0) / (params.n_or_a2 * params.NdotH2) ); // exp(x) == exp2(x/log(2)) ?
scalar_type denom = params.n_or_a2 * params.NdotH2 * params.NdotH2;
return numbers::inv_pi<scalar_type> * nom / denom;
}

scalar_type operator()(SAnisotropicParams<scalar_type> params)
{
scalar_type nom = exp<scalar_type>(-(params.TdotH2 / params.ax2 + params.BdotH2 / params.ay2) / params.NdotH2);
scalar_type denom = params.ax * params.ay * params.NdotH2 * params.NdotH2;
return numbers::inv_pi<scalar_type> * nom / denom;
}
};


template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>)
struct GGX
{
using scalar_type = T;

// trowbridge-reitz
scalar_type operator()(SIsotropicParams<scalar_type> params)
{
scalar_type denom = params.NdotH2 * (params.n_or_a2 - 1.0) + 1.0;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

literals need scalar_type(1.0) otherwise when scalar_type=double you loose precision and with scalar_type=float16_t you accidentally end up with float32_t promotions and truncations for parts of the expression

Comment on lines +155 to +156
// burley
scalar_type operator()(SAnisotropicParams<scalar_type> params, scalar_type anisotropy)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should have just been done with different creation parameters or a different NDF struct

Comment on lines +112 to +139
}
};

template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>)
struct Beckmann
{
using scalar_type = T;

scalar_type operator()(SIsotropicParams<scalar_type> params)
{
scalar_type nom = exp<scalar_type>( (params.NdotH2 - 1.0) / (params.n_or_a2 * params.NdotH2) ); // exp(x) == exp2(x/log(2)) ?
scalar_type denom = params.n_or_a2 * params.NdotH2 * params.NdotH2;
return numbers::inv_pi<scalar_type> * nom / denom;
}

scalar_type operator()(SAnisotropicParams<scalar_type> params)
{
scalar_type nom = exp<scalar_type>(-(params.TdotH2 / params.ax2 + params.BdotH2 / params.ay2) / params.NdotH2);
scalar_type denom = params.ax * params.ay * params.NdotH2 * params.NdotH2;
return numbers::inv_pi<scalar_type> * nom / denom;
}
};


template<typename T NBL_PRIMARY_REQUIRES(is_scalar_v<T>)
struct GGX
{
using scalar_type = T;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because of the differing number of member parameters and implementations of operator() for each NDF you must have a different struct (add a trait ndf::is_anisotropic_v)

Copy link
Member Author

@devshgraphicsprogramming devshgraphicsprogramming Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

example, Isotropic GGX only needs a2 while anisotropic needs ax2,ay2,a2 (created from ax,ay or ax2 and ay2)


scalar_type operator()(SIsotropicParams<scalar_type> params)
{
scalar_type nom = exp<scalar_type>( (params.NdotH2 - 1.0) / (params.n_or_a2 * params.NdotH2) ); // exp(x) == exp2(x/log(2)) ?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can slap the log<scalar_type>(2.f) into the a2*NdotH2 expression here

Comment on lines +170 to +180
template<class T, class U>
struct is_ggx : bool_constant<
is_same<T, GGX<U> >::value
> {};
}

template<class T>
struct is_ggx : impl::is_ggx<T, typename T::scalar_type> {};

template<typename T>
NBL_CONSTEXPR bool is_ggx_v = is_ggx<T>::value;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be a general trait, not hidden in impl::

Comment on lines +1 to +17
// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O.
// This file is part of the "Nabla Engine".
// For conditions of distribution and use, see copyright notice in nabla.h
#ifndef _NBL_BUILTIN_HLSL_BXDF_NDF_INCLUDED_
#define _NBL_BUILTIN_HLSL_BXDF_NDF_INCLUDED_

#include "nbl/builtin/hlsl/limits.hlsl"
#include "nbl/builtin/hlsl/bxdf/common.hlsl"

namespace nbl
{
namespace hlsl
{
namespace bxdf
{
namespace ndf
{
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the NDF "fixes" the Lambda and Beta functions so it makes sense to have an ndf(this_t::query_type) (instead of just operator(this_t::query_type)) and lambda() methods instead of having the lambda buried somewhere else in anothre header

#811 (comment)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actualy ndf should be called D to be consistent with that PBR papers (Cook Torrance)

then vndf is DG1

Copy link
Member Author

@devshgraphicsprogramming devshgraphicsprogramming Jul 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So to recap:

  • D (the ndf)
  • Lambda
  • Beta
  • DG1

Comment on lines +39 to +92
// iso
Scalar getNdotV() NBL_CONST_MEMBER_FUNC { return hlsl::mix(math::conditionalAbsOrMax<Scalar>(_clamp == BxDFClampMode::BCM_ABS, interaction.getNdotV(), 0.0), interaction.getNdotV(), _clamp == BxDFClampMode::BCM_NONE); }
Scalar getNdotVUnclamped() NBL_CONST_MEMBER_FUNC { return interaction.getNdotV(); }
Scalar getNdotV2() NBL_CONST_MEMBER_FUNC { return interaction.getNdotV2(); }
Scalar getNdotL() NBL_CONST_MEMBER_FUNC { return hlsl::mix(math::conditionalAbsOrMax<Scalar>(_clamp == BxDFClampMode::BCM_ABS, _sample.getNdotL(), 0.0), _sample.getNdotL(), _clamp == BxDFClampMode::BCM_NONE); }
Scalar getNdotLUnclamped() NBL_CONST_MEMBER_FUNC { return _sample.getNdotL(); }
Scalar getNdotL2() NBL_CONST_MEMBER_FUNC { return _sample.getNdotL2(); }
Scalar getVdotL() NBL_CONST_MEMBER_FUNC { return _sample.getVdotL(); }
Scalar getNdotH() NBL_CONST_MEMBER_FUNC { return cache.getNdotH(); }
Scalar getNdotH2() NBL_CONST_MEMBER_FUNC { return cache.getNdotH2(); }
Scalar getVdotH() NBL_CONST_MEMBER_FUNC { return cache.getVdotH(); }
Scalar getLdotH() NBL_CONST_MEMBER_FUNC { return cache.getLdotH(); }

LS _sample;
SI interaction;
MC cache;
BxDFClampMode _clamp;
};
template<class LS, class SI, class MC, typename Scalar>
NBL_PARTIAL_REQ_TOP(surface_interactions::Anisotropic<SI> && AnisotropicMicrofacetCache<MC>)
struct GGXParams<LS, SI, MC, Scalar NBL_PARTIAL_REQ_BOT(surface_interactions::Anisotropic<SI> && AnisotropicMicrofacetCache<MC>) >
{
using this_t = GGXParams<LS, SI, MC, Scalar>;

static this_t create(NBL_CONST_REF_ARG(LS) _sample, NBL_CONST_REF_ARG(SI) interaction, NBL_CONST_REF_ARG(MC) cache, BxDFClampMode _clamp)
{
this_t retval;
retval._sample = _sample;
retval.interaction = interaction;
retval.cache = cache;
retval._clamp = _clamp;
return retval;
}

// iso
Scalar getNdotV() NBL_CONST_MEMBER_FUNC { return hlsl::mix(math::conditionalAbsOrMax<Scalar>(_clamp == BxDFClampMode::BCM_ABS, interaction.getNdotV(), 0.0), interaction.getNdotV(), _clamp == BxDFClampMode::BCM_NONE); }
Scalar getNdotVUnclamped() NBL_CONST_MEMBER_FUNC { return interaction.getNdotV(); }
Scalar getNdotV2() NBL_CONST_MEMBER_FUNC { return interaction.getNdotV2(); }
Scalar getNdotL() NBL_CONST_MEMBER_FUNC { return hlsl::mix(math::conditionalAbsOrMax<Scalar>(_clamp == BxDFClampMode::BCM_ABS, _sample.getNdotL(), 0.0), _sample.getNdotL(), _clamp == BxDFClampMode::BCM_NONE); }
Scalar getNdotLUnclamped() NBL_CONST_MEMBER_FUNC { return _sample.getNdotL(); }
Scalar getNdotL2() NBL_CONST_MEMBER_FUNC { return _sample.getNdotL2(); }
Scalar getVdotL() NBL_CONST_MEMBER_FUNC { return _sample.getVdotL(); }
Scalar getNdotH() NBL_CONST_MEMBER_FUNC { return cache.getNdotH(); }
Scalar getNdotH2() NBL_CONST_MEMBER_FUNC { return cache.getNdotH2(); }
Scalar getVdotH() NBL_CONST_MEMBER_FUNC { return cache.getVdotH(); }
Scalar getLdotH() NBL_CONST_MEMBER_FUNC { return cache.getLdotH(); }

// aniso
Scalar getTdotL2() NBL_CONST_MEMBER_FUNC { return _sample.getTdotL() * _sample.getTdotL(); }
Scalar getBdotL2() NBL_CONST_MEMBER_FUNC { return _sample.getBdotL() * _sample.getBdotL(); }
Scalar getTdotV2() NBL_CONST_MEMBER_FUNC { return interaction.getTdotV() * interaction.getTdotV(); }
Scalar getBdotV2() NBL_CONST_MEMBER_FUNC { return interaction.getBdotV() * interaction.getBdotV(); }
Scalar getTdotH2() NBL_CONST_MEMBER_FUNC {return cache.getTdotH() * cache.getTdotH(); }
Scalar getBdotH2() NBL_CONST_MEMBER_FUNC {return cache.getBdotH() * cache.getBdotH(); }
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we definitely need getXdotY(const BxDFClampMode) on our interaction, sample and cache concepts and skip this insanity

Comment on lines +331 to +332
vector2_type A;
spectral_type ior0, ior1;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need different BxDF or specializations for anisotropic and isotropic, different amounts of variables to precompute.

Comment on lines +200 to +207
static this_t create(scalar_type NDFcos, scalar_type maxNdotV)
{
this_t retval;
retval.NDFcos = NDFcos;
if (is_ggx_v<NDF>)
retval.maxNdotL = maxNdotV;
else
retval.maxNdotV = maxNdotV;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this makes no sense to me when I'm reading it, why do we have NdotV thats then being assigned to NdotL when we have GGX

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this is wrong for GGX, you can't just take the dot product of the view vector and geometrical normal and use it as the dot product of view and geometrical

Comment on lines +211 to +217
scalar_type operator()()
{
if (is_ggx_v<NDF>)
return NDFcos * maxNdotL;
else
return 0.25 * NDFcos / maxNdotV;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

write a comment that this computes the max(NdotL,0)/(4*max(NdotV,0)*max(NdotL,0)) factor which transforms PDFs in the f in projected microfacet f * NdotH measure to projected light measure f * NdotL

Comment on lines +194 to +200
template<typename NDF>
struct microfacet_to_light_measure_transform<NDF,REFLECT_BIT>
{
using this_t = microfacet_to_light_measure_transform<NDF,REFLECT_BIT>;
using scalar_type = typename NDF::scalar_type;

static this_t create(scalar_type NDFcos, scalar_type maxNdotV)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw this doesn't need to be a struct, can be a plain old function (if you need partial specs, can be like the tgmath functions calling an impl::helper)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because right now you're constructing the structs immediately with create and calling operator() on them immediately, also there's literally no input argument you could cache for multiple calls

Comment on lines +103 to +104
using isocache_type = IsoCache;
using anisocache_type = AnisoCache;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why does a smooth BxDF care about the microfacet cache ?

retval.value = retval.backside ? rcpEta : eta;
retval.rcp = retval.backside ? eta : rcpEta;
retval.value = backside ? rcpEta : eta;
retval.rcp = backside ? eta : rcpEta;
return retval;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a OrientedEtaRcps<T> getReciprocals() nBL_const_method

Comment on lines 125 to 131
static this_t create(NBL_CONST_REF_ARG(fresnel::OrientedEtaRcps<monochrome_type>) rcpEtas, NBL_CONST_REF_ARG(vector_type) I, NBL_CONST_REF_ARG(vector_type) N, const bool backside)
{
this_t retval;
retval.I = I;
retval.N = N;
retval.NdotI = hlsl::dot<vector_type>(N, I);
retval.NdotI2 = retval.NdotI * retval.NdotI;
retval.rcpOrientedEta = rcpEtas;
retval.recomputeNdotT(rcpEtas.backside, retval.NdotI2, rcpEtas.value2);
return retval;
}

static this_t create(NBL_CONST_REF_ARG(fresnel::OrientedEtaRcps<scalar_type>) rcpEtas, NBL_CONST_REF_ARG(vector_type) I, NBL_CONST_REF_ARG(vector_type) N, const scalar_type NdotI)
{
this_t retval;
retval.I = I;
retval.N = N;
retval.NdotI = NdotI;
retval.NdotI2 = retval.NdotI * retval.NdotI;
retval.rcpOrientedEta = rcpEtas;
retval.recomputeNdotT(rcpEtas.backside, retval.NdotI2, rcpEtas.value2);
retval.recomputeNdotI();
retval.recomputeNdotT(backside, retval.NdotI2, rcpEtas.value2[0]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

backside can be worked out from sign of NdotI after you call recomputeNdotI

Comment on lines 319 to 322
template<typename T NBL_PRIMARY_REQUIRES(concepts::FloatingPointScalar<T> || concepts::FloatingPointLikeVectorial<T>)
struct DielectricFrontFaceOnly
{
using scalar_type = typename vector_traits<T>::scalar_type;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

look at your tpedef, your code still wont compile with a scalar T, just give up and require vectorial for Schlick, Conductor, Dielectric and so on

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -160,18 +145,17 @@ struct Refract
NdotT = ieee754::flipSign(absNdotT, backside);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure this is wrong, because for a tranmission NdotT has to to have opposite sign to NdotI, and here you're turning NdotT negative when backside==true and backside = NdotI<0.f

you want to do

NdotT = ieee754::copySign(NdotT,-NdotI);

p.S. best if you make a ieee754::copySignIntoPositive which is specialized for inputs we know to have a 0 sign bit (skips us doing one bitwise op)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also NdotI2 you still have as a member and shouldn't take from the outside

Comment on lines 36 to 54
static ComputeMicrofacetNormal<T> create(NBL_CONST_REF_ARG(vector_type) V, NBL_CONST_REF_ARG(vector_type) L, NBL_CONST_REF_ARG(vector_type) H, scalar_type eta)
{
ComputeMicrofacetNormal<T> retval;
retval.V = V;
retval.L = L;
retval.orientedEta = fresnel::OrientedEtas<scalar_type>::create(hlsl::dot<vector_type>(V, H), eta);
fresnel::OrientedEtas<monochrome_type> orientedEtas = fresnel::OrientedEtas<monochrome_type>::create(hlsl::dot<vector_type>(V, H), eta);
retval.orientedEta = orientedEtas.value;
return retval;
}

static ComputeMicrofacetNormal<T> create(NBL_CONST_REF_ARG(vector_type) V, NBL_CONST_REF_ARG(vector_type) L, scalar_type VdotH, scalar_type eta)
{
ComputeMicrofacetNormal<T> retval;
retval.V = V;
retval.L = L;
fresnel::OrientedEtas<monochrome_type> orientedEtas = fresnel::OrientedEtas<monochrome_type>::create(VdotH, eta);
retval.orientedEta = orientedEtas.value;
return retval;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not have the vec3 H call the one taking VdotH after computing dot(V,H)

Comment on lines 97 to 133
NBL_CONCEPT_BEGIN(6)
#define NBL_CONCEPT_PARAM_6 (backside, bool)
NBL_CONCEPT_BEGIN(7)
#define rdirinfo NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0
#define N NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1
#define dirDotN NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2
#define m NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3
#define rfl NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_4
#define rfr NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_5
#define backside NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_6
NBL_CONCEPT_END(
((NBL_CONCEPT_REQ_TYPE)(T::scalar_type))
((NBL_CONCEPT_REQ_TYPE)(T::vector3_type))
((NBL_CONCEPT_REQ_TYPE)(T::matrix3x3_type))
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((rdirinfo.getDirection()), ::nbl::hlsl::is_same_v, typename T::vector3_type))
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((rdirinfo.transmit()), ::nbl::hlsl::is_same_v, T))
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((rdirinfo.reflect(rfl)), ::nbl::hlsl::is_same_v, T))
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((rdirinfo.refract(rfr)), ::nbl::hlsl::is_same_v, T))
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((rdirinfo.refract(rfr, backside, dirDotN)), ::nbl::hlsl::is_same_v, T))
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((rdirinfo.transform(m)), ::nbl::hlsl::is_same_v, T))
((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(is_scalar_v, typename T::scalar_type))
((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(is_vector_v, typename T::vector3_type))
);
#undef backside
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dirDotN sign bit is the backside

retval.NdotH2 = retval.NdotH * retval.NdotH;
}
else
retval.NdotH = -1.0;
retval.NdotH = nbl::hlsl::numeric_limits<scalar_type>::quiet_NaN;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw the bitpatterns are uint, they need to be bitcasted

Comment on lines 648 to 651
static bool isValidMicrofacetCache(const bool transmitted, const scalar_type VdotL, const scalar_type NdotH, const scalar_type eta, const scalar_type rcp_eta)
{
return !transmitted || (VdotL <= -hlsl::min(eta, rcp_eta) && NdotH >= nbl::hlsl::numeric_limits<scalar_type>::min);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote method of ComputeMicrofacetNormal not the microfacet cache #899 (comment)

Comment on lines 163 to 151
vector_type operator()()
vector_type operator()(const bool backside, scalar_type rcpOrientedEta)
{
recomputeNdotT(rcpOrientedEta.backside, NdotI2, rcpOrientedEta.value2);
return N * (NdotI * rcpOrientedEta.value + NdotT) - rcpOrientedEta.value * I;
recomputeNdotT(rcpOrientedEta.backside, NdotI2, rcpOrientedEta*rcpOrientedEta);
return N * (NdotI * rcpOrientedEta + NdotT) - rcpOrientedEta * I;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the reflect does not call recomputeNdotI, maybe you should expect its already called for summetry

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants